﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace XEnamel.Tests
{
	[TestClass]
	public class DynamicMethodTests
	{
		[TestMethod]
		[ExpectedException(typeof(ArgumentException), "Invalid zip code.")]
		public void Can_process_zip_codes()
		{
			dynamic scope = DynamicMethod.Load("../../../XEnamel.Tests/bin/Debug/Content/ZipToState.xml");
			Assert.AreEqual("TX", scope("75231"));
			Assert.AreEqual("OR", scope("97534"));
			Assert.AreEqual("KS", scope("66321"));
			Assert.AreEqual("CA", scope("94381"));
			Assert.AreEqual("NC", scope("27452"));
			scope("00000");
		}

		[TestMethod]
		public void Can_process_hello_user()
		{
			dynamic scope = DynamicMethod.Load("../../../XEnamel.Tests/bin/Debug/Content/HelloUser.xml");

			var output = new StringBuilder();
			var writer = new StringWriter(output);
			Console.SetOut(writer);

			scope("Trey");
			Assert.AreEqual("Hello, Trey!\r\n", output.ToString());

			output.Clear();
			Console.SetIn(new StringReader("Bob"));
			scope(null);
			Assert.AreEqual("Please enter your name: Hello, Bob!\r\n", output.ToString());

			output.Clear();
			Console.SetIn(new StringReader("Steve"));
			scope(string.Empty);
			Assert.AreEqual("Please enter your name: Hello, Steve!\r\n", output.ToString());
		}

		[TestMethod]
		public void Can_process_factorial()
		{
			dynamic scope = DynamicMethod.Load("../../../XEnamel.Tests/bin/Debug/Content/Factorial.xml");
			Assert.AreEqual(1, scope(0));
			Assert.AreEqual(1, scope(1));
			Assert.AreEqual(2, scope(2));
			Assert.AreEqual(6, scope(3));
			Assert.AreEqual(24, scope(4));
			Assert.AreEqual(120, scope(5));
			Assert.AreEqual(720, scope(6));
			Assert.AreEqual(5040, scope(7));
			Assert.AreEqual(40320, scope(8));
			Assert.AreEqual(362880, scope(9));
		}

		[TestMethod]
		public void Can_process_write_some_stuff()
		{
			dynamic scope = DynamicMethod.Load("../../../XEnamel.Tests/bin/Debug/Content/WriteSomeStuff.xml");

			var output = new StringBuilder();
			var writer = new StringWriter(output);
			Console.SetOut(writer);

			scope(20);
			Assert.AreEqual("Print arg: 20\r\n", output.ToString());
		}

		[TestMethod]
		public void Can_process_simple_object()
		{
			dynamic scope = DynamicMethod.Load("../../../XEnamel.Tests/bin/Debug/Content/SimpleObjectTest.xml");

			scope.n1 = 5;
			scope.n2 = 7;
			scope();

			Assert.AreEqual(37, (int)scope.add(13, 24));
			var names = ((IEnumerable<string>)scope.GetDynamicMemberNames()).ToList();
			Assert.AreEqual(4, names.Count());
			CollectionAssert.Contains(names, "n1");
			CollectionAssert.Contains(names, "n2");
			CollectionAssert.Contains(names, "add");
			CollectionAssert.Contains(names, "result");

			Assert.AreEqual(5, scope.n1);
			Assert.AreEqual(7, scope.n2);
			Assert.AreEqual(12, scope.result);
			Assert.AreEqual(typeof(Func<object, object, object>), scope.add.GetType());
		}

		[TestMethod]
		public void Can_serialize_expression()
		{
			var self = Expression.Parameter(typeof(DynamicMethod), "self");

			var n1 = Expression.Parameter(typeof(object), "n1");
			var n2 = Expression.Parameter(typeof(object), "n2");

			var addBody = new List<Expression>()
			{
				Expression.Assign(
					ExpressionEx.SymbolExpression(self, "add"),
					Expression.Lambda(Expression.Block(
						Expression.Assign(ExpressionEx.SymbolExpression(self, "n3"), ExpressionEx.Add(n1, n2)),
						ExpressionEx.Add(n1, n2)
					), n1, n2)
				),
				Expression.Assign(
					ExpressionEx.SymbolExpression(self, "result"),
					ExpressionEx.DynamicInvoke(
						ExpressionEx.SymbolExpression(self, "add"),
						ExpressionEx.SymbolExpression(self, "n1"),
						ExpressionEx.SymbolExpression(self, "n2")
					)
				)
			};

			var xml = DynamicMethod.Load(Expression.Lambda<Func<DynamicMethod, object>>(Expression.Block(addBody), self)).ToXml();

			dynamic scope = DynamicMethod.Load(xml);
			scope.n1 = 5;
			scope.n2 = 7;
			scope();

			Assert.AreEqual(37, (int)scope.add(13, 24));
			var names = ((IEnumerable<string>)scope.GetDynamicMemberNames()).ToList();
			Assert.AreEqual(5, names.Count());
			CollectionAssert.Contains(names, "n1");
			CollectionAssert.Contains(names, "n2");
			CollectionAssert.Contains(names, "n3");
			CollectionAssert.Contains(names, "add");
			CollectionAssert.Contains(names, "result");

			Assert.AreEqual(5, scope.n1);
			Assert.AreEqual(7, scope.n2);
			Assert.AreEqual(37, scope.n3);
			Assert.AreEqual(12, scope.result);
			Assert.AreEqual(typeof(Func<object, object, object>), scope.add.GetType());
		}
	}
}